/*
	This file is part of PropExtractor.

	PropExtractor is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	PropExtractor is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with PropExtractor. If not, see <http://www.gnu.org/licenses/>.
*/

#include "PEIncludes.h"

#include "PEFile.h"

namespace pe
{

	int PEFileInfoParse(PEFileInfo* a_Target, const char* a_String)
	{
		const char* src = a_String;
		const char* src_start = NULL;
		int state = 0;
		int state_next = -1;
		int state_prev = -1;
		do 
		{
			if (state_next > -1)
			{
				state = state_next;
				state_next = -1;
			}

			switch (state)
			{
			case 0:
				{
					if (*src == '(')
					{
						src_start = src + 1;
						state_next = 1;
					}
					else if (*src == '[')
					{
						src_start = src + 1;
						state_next = 2;
					}

					break;
				}

			case 1:
				{
					if (*src == ')')
					{
						a_Target->company = new char[src - src_start + 1];
						memset(a_Target->company, 0, src - src_start + 1);
						strncpy(a_Target->company, src_start, src - src_start);

						src_start = src + 1;

						state_next = 0;
					}

					break;
				}

			case 2:
				{
					if (*src == ']')
					{
						a_Target->type = new char[src - src_start + 1];
						memset(a_Target->type, 0, src - src_start + 1);
						strncpy(a_Target->type, src_start, src - src_start);

						src_start = src + 1;

						state_next = 0;
					}

					break;
				}
			}

			src++;

			if (*src == 0)
			{
				PE_STRCLRCPY(a_Target->comment, src_start);
			}
		} 
		while (*src);

		return PE_OK;
	}

	// ================================
	// Streams
	// ================================

	PE_EXPORT int PEStreamOpen(PEStream* a_Target, const wchar_t* a_FilePath, const wchar_t* a_Options, bool a_SetPath)
	{
		a_Target->data = NULL;

		a_Target->handle = _wfopen(a_FilePath, a_Options);
		if (!a_Target->handle) { return -1; }

		a_Target->dir = NULL;
		a_Target->ext = NULL;
		a_Target->name = NULL;
		a_Target->path = NULL;

		if (a_SetPath) 
		{ 
			PEStreamSetPath(a_Target, a_FilePath); 
		}

		fseek(a_Target->handle, 0, SEEK_END);
		a_Target->filesize = ftell(a_Target->handle);
		fseek(a_Target->handle, 0, SEEK_SET);
		a_Target->baseoffset = 0;
		a_Target->offset = 0;
		a_Target->maximum = 0;

		a_Target->bytesread = 0;

		return PE_OK;
	}

	PE_EXPORT int PEStreamSetOffset(PEStream* a_Target, unsigned int a_Offset, unsigned int a_Maximum)
	{
		a_Target->offset = 0;
		a_Target->baseoffset = a_Offset;
		a_Target->maximum = a_Target->baseoffset + a_Maximum;
		a_Target->filesize = a_Maximum;

		if (a_Target->handle)
		{
			fseek(a_Target->handle, a_Target->baseoffset, SEEK_SET);
		}

		return PE_OK;
	}

	PE_EXPORT int PEStreamByteOpen(PEStream* a_Target, byte* a_Source, unsigned int a_SizeInBytes)
	{
		a_Target->data = a_Source;
		a_Target->handle = NULL;

		a_Target->filesize = a_SizeInBytes;
		a_Target->baseoffset = 0;
		a_Target->offset = 0;
		a_Target->maximum = 0;

		a_Target->bytesread = 0;

		return PE_OK;
	}

	PE_EXPORT int PEStreamCopy(PEStream* a_Target, PEStream* a_Source)
	{
		a_Target->handle = a_Source->handle;
		a_Target->data = a_Source->data;

		PE_WSTRCLRCPY(a_Target->dir, a_Source->dir);
		PE_WSTRCLRCPY(a_Target->ext, a_Source->ext);
		PE_WSTRCLRCPY(a_Target->name, a_Source->name);
		PE_WSTRCLRCPY(a_Target->path, a_Source->path);

		a_Target->bytesread = a_Source->bytesread;
		a_Target->filesize = a_Source->filesize;
		a_Target->offset = a_Source->offset;

		return PE_OK;
	}


	PE_EXPORT int PEStreamSetPathA(PEStream* a_Target, const char* a_FilePath)
	{
		wchar_t* str;
		PE_STRTOWIDE(str, a_FilePath);

		PEStreamSetPath(a_Target, str);

		delete str;

		return PE_OK;
	}

	PE_EXPORT int PEStreamSetPath(PEStream* a_Target, const wchar_t* a_FilePath)
	{
		PE_WSTRCLRCPY(a_Target->path, a_FilePath);

		unsigned int dir_length = 0;

		bool slashfound = false;

		wchar_t* lastslash = wcsrchr((wchar_t*)a_FilePath, L'\\');
		if (!lastslash) 
		{
			lastslash = wcsrchr((wchar_t*)a_FilePath, L'/');
		}

		if (!lastslash)
		{
			lastslash = (wchar_t*)a_FilePath; 
			dir_length = wcslen(a_FilePath);
		}
		else
		{
			lastslash++;
			dir_length = lastslash - a_FilePath;
		}
		wchar_t* lastdot = wcsrchr((wchar_t*)a_FilePath, L'.');
		if (!lastdot)
		{
			// BUGFIX: i instead of . in file?
			lastdot = wcsrchr((wchar_t*)a_FilePath, (wchar_t)0x69);
		}

		a_Target->dir = new wchar_t[dir_length + 1];
		memset(a_Target->dir, 0, (dir_length + 1) * 2);
		wcsncat(a_Target->dir, a_FilePath, dir_length);

		a_Target->name = new wchar_t[lastdot - lastslash + 1];
		memset(a_Target->name, 0, (lastdot - lastslash + 1) * 2);
		wcsncat(a_Target->name, lastslash, lastdot - lastslash);

		a_Target->ext = new wchar_t[wcslen(lastdot)];
		memset(a_Target->ext, 0, wcslen(lastdot) * 2);
		wcsncat(a_Target->ext, lastdot + 1, wcslen(lastdot) - 1);

		return PE_OK;
	}

	PE_EXPORT int PEStreamRead(PEStream* a_Src, void* a_Dst, unsigned int a_Size, unsigned int a_Count)
	{
		if (a_Src->maximum > 0 && a_Src->offset + a_Size * a_Count >= a_Src->maximum)
		{
			return -1;
		}

		if (a_Src->handle)
		{
			unsigned int offset_pre = ftell(a_Src->handle);
			fread(a_Dst, a_Size, a_Count, a_Src->handle);
			unsigned int offset_post = ftell(a_Src->handle);
			if (ferror(a_Src->handle))
			{
				return -1;
			}
			a_Src->bytesread = a_Size * a_Count;
		}
		else
		{
			unsigned int i = 0;
			byte* dst = (byte*)a_Dst;
			byte* src = (byte*)a_Src->data + a_Src->offset;
			unsigned int total = a_Size * a_Count;

			if (a_Src->offset >= a_Src->filesize)
			{
				return -1;
			}

			if (a_Src->offset + total >= a_Src->filesize) 
			{
				total = a_Src->filesize - a_Src->offset;
			}

			for (; i < total; i++)
			{
				*dst++ = *src++;
			}

			a_Src->bytesread = total;
		}

		a_Src->offset += a_Src->bytesread;

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetBytesRead(PEStream* a_Src, unsigned int* a_Target)
	{
		*a_Target = a_Src->bytesread;

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetCurrentPos(PEStream* a_Src, unsigned int* a_Target)
	{
		*a_Target = a_Src->offset;

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetFileSize(PEStream* a_Src, unsigned int* a_Target)
	{
		*a_Target = a_Src->filesize;

		return PE_OK;
	}


	PE_EXPORT int PEStreamSeek(PEStream* a_Target, unsigned int a_Offset, int a_Type)
	{
		switch (a_Type)
		{
		case SEEK_SET:
			{
				a_Target->offset = a_Offset;
				break;
			}
			
		case SEEK_CUR:
			{
				a_Target->offset += a_Offset;
				break;
			}
			
		case SEEK_END:
			{
				a_Target->offset = a_Target->filesize - a_Offset;
				break;
			}
			
		default:
			return -1;
		};

		if (a_Target->handle)
		{
			fseek(a_Target->handle, a_Target->baseoffset + a_Offset, a_Type);
		}

		return PE_OK;
	}

	PE_EXPORT int PEStreamClose(PEStream* a_Target)
	{
		if (a_Target->handle)
		{
			fclose(a_Target->handle);
		}

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetFileName(PEStream* a_Src, wchar_t** a_Target)
	{
		*a_Target = new wchar_t[wcslen(a_Src->name) + 1];
		memset(*a_Target, 0, (wcslen(a_Src->name) + 1) * 2);
		wcscpy(*a_Target, a_Src->name);

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetPath(PEStream* a_Src, wchar_t** a_Target)
	{
		*a_Target = new wchar_t[wcslen(a_Src->path) + 1];
		memset(*a_Target, 0, (wcslen(a_Src->path) + 1) * 2);
		wcscpy(*a_Target, a_Src->path);

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetDir(PEStream* a_Src, wchar_t** a_Target)
	{
		*a_Target = new wchar_t[wcslen(a_Src->dir) + 1];
		memset(*a_Target, 0, (wcslen(a_Src->dir) + 1) * 2);
		wcscpy(*a_Target, a_Src->dir);

		return PE_OK;
	}

	PE_EXPORT int PEStreamGetExt(PEStream* a_Src, wchar_t** a_Target)
	{
		*a_Target = new wchar_t[wcslen(a_Src->ext) + 1];
		memset(*a_Target, 0, (wcslen(a_Src->ext) + 1) * 2);
		wcscpy(*a_Target, a_Src->ext);

		return PE_OK;
	}

};